use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use sha2::Sha256;
use wither::{
    bson::{doc, oid::ObjectId, DateTime, Document},
    Model,
};
mod serde_default {
    use super::*;
    pub fn account_type() -> AccountType {
        AccountType::Site
    }
}
use crate::utils::{
    crypto::{hash_password, random_string},
    traits::{ToAccountModel, ToSearchDoc, ToUpdateDoc, ToUserInfo},
};
use hex::encode;

use super::{user::UserModel, Assets};

#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum AccountType {
    Site = 0,
    OAuth = 1,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct WechatWebOAuthToken {
    /// 此access_token与服务用的access_token不一样。
    /// 只是用于网页OAuth授权拿用户信息的
    pub access_token: String,
    pub refresh_token: String,
    pub access_expires_time: DateTime,
    /// refresh_token一般是30天过期
    pub refresh_expires_time: DateTime,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct WechatPlatformUserInfo {
    pub open_id: String,
    pub nickname: String,
    pub sex: u8,
    pub language: String,
    pub city: String,
    pub province: String,
    pub country: String,
    pub head_img_url: String,
    pub unionid: String,
    pub oauth_token: Option<WechatWebOAuthToken>,
    pub subscrib_time: Option<i64>,
    pub remark: Option<String>,
    pub ground_id: Option<i64>,
    pub tagid_list: Option<Vec<i32>>,
    pub subscribe_scene: Option<String>,
    pub qr_scene: Option<i64>,
    pub qr_scene_str: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct GithubPlatformUserInfo {
    pub login: String,
    pub id: i64,
    pub node_id: String,
    pub avatar_url: String,
    pub gravatar_id: String,
    pub url: String,
    pub html_url: String,
    pub followers_url: String,
    pub following_url: String,
    pub gists_url: String,
    pub starred_url: String,
    pub subscriptions_url: String,
    pub organizations_url: String,
    pub repos_url: String,
    pub events_url: String,
    pub received_events_url: String,
    pub r#type: String,
    pub site_admin: bool,
    pub name: String,
    pub company: Option<String>,
    pub blog: String,
    pub location: Option<String>,
    pub email: Option<String>,
    pub hireable: Option<bool>,
    pub twitter_username: Option<String>,
    pub public_repos: i64,
    pub public_gists: i64,
    pub followers: i64,
    pub following: i64,
    pub created_at: DateTime,
    pub updated_at: DateTime,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct GiteePlatformUserInfo {
    pub avatar_url: String,
    pub bio: String,
    pub blog: Option<String>,
    pub email: Option<String>,
    pub events_url: String,
    pub followers: i64,
    pub followers_url: String,
    pub following: i64,
    pub following_url: String,
    pub gists_url: String,
    pub html_url: String,
    pub id: i64,
    pub login: String,
    pub member_role: Option<String>,
    pub name: String,
    pub organizations_url: String,
    pub public_gists: i64,
    pub public_repos: i64,
    pub received_events_url: String,
    pub remark: String,
    pub repos_url: String,
    pub stared: i64,
    pub starred_url: String,
    pub subscriptions_url: String,
    pub r#type: String,
    pub url: String,
    pub watched: i64,
    pub weibo: Option<String>,
    pub created_at: DateTime,
    pub updated_at: DateTime,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct QQPlatformUserInfo {}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(tag = "p_type")]
pub enum PlatformUserInfo {
    Wechat(WechatPlatformUserInfo),
    QQ(QQPlatformUserInfo),
    Github(GithubPlatformUserInfo),
    Gitee(GiteePlatformUserInfo),
}

impl ToUserInfo for PlatformUserInfo {
    fn to_user_info(&self) -> UserModel {
        match self {
            Self::Github(g) => UserModel {
                nickname: g.name.clone(),
                email: g.email.clone(),
                avatar: Some(Assets::new_with_site_path(g.avatar_url.clone())),
                ..Default::default()
            },
            Self::Gitee(g) => UserModel {
                nickname: g.name.clone(),
                email: g.email.clone(),
                avatar: Some(Assets::new_with_site_path(g.avatar_url.clone())),
                ..Default::default()
            },
            _ => UserModel {
                ..Default::default()
            },
        }
    }
}
impl ToSearchDoc for PlatformUserInfo {
    fn to_search_doc(&self) -> Document {
        let mut doc = Document::new();
        match self {
            Self::Github(g) => {
                doc.insert("platform_info.p_type", "Github");
                doc.insert("platform_info.id", g.id);
            }
            Self::Gitee(g) => {
                doc.insert("platform_info.p_type", "Gitee");
                doc.insert("platform_info.id", g.id);
            }
            _ => {}
        }
        doc
    }
}
impl ToUpdateDoc for PlatformUserInfo {
    fn to_update_doc(&self) -> Document {
        doc! {
            "platform_info":wither::bson::to_document(self).ok()
        }
    }
}
impl ToAccountModel for PlatformUserInfo {
    fn to_account_model(&self) -> AccountModel {
        AccountModel {
            account_type: AccountType::OAuth,
            ..Default::default()
        }
    }
}
impl_into_bson!(AccountType);

#[derive(Debug, Clone, Serialize, Deserialize, Model)]
#[model(index(keys = r#"doc!{"account":1}"#, options = r#"doc!{"unique":true}"#))]
pub struct AccountModel {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<ObjectId>,
    pub user_id: Option<ObjectId>,
    pub account: String,
    pub pass_salt: Option<String>,
    pub password: Option<String>,
    #[serde(rename = "type", default = "serde_default::account_type")]
    pub account_type: AccountType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub platform_info: Option<PlatformUserInfo>,
    pub up_time: Option<DateTime>,
    pub created_at: DateTime,
    pub updated_at: DateTime,
}
impl AccountModel {
    /// ## set_password_with_salt
    /// 将原始password+salt经过sha256处理之后设置进去
    pub fn set_password_with_salt(&mut self, password: &str) {
        if let Some(ref slat) = self.pass_salt {
            self.password = Some(Self::encode_password(password, slat));
        }
    }
    pub fn encode_password(password: &str, slat: &str) -> String {
        let mut output = [0; 32];
        hash_password::<Sha256>(password, slat, &mut output);
        encode(output)
    }
    /// ## valid_password
    /// 将输入的密码与原始密码比较验证
    pub fn valid_password(&self, password: &str) -> bool {
        let mut output = [0u8; 32];
        if let (Some(slat), Some(self_password)) = (&self.pass_salt, &self.password) {
            hash_password::<Sha256>(password, slat, &mut output);
            &encode(output) == self_password
        } else {
            false
        }
    }
    /// ## gen_slat
    /// 生成一个盐值
    pub fn gen_slat() -> String {
        random_string()
    }
    pub fn set_salt(&mut self) {
        self.pass_salt = Some(Self::gen_slat());
    }
    pub fn try_get_email(&self) -> &Option<String> {
        match &self.platform_info {
            Some(PlatformUserInfo::Github(g)) => &g.email,
            Some(PlatformUserInfo::Gitee(g)) => &g.email,
            _ => &None,
        }
    }
}
impl Default for AccountModel {
    fn default() -> Self {
        Self {
            id: None,
            user_id: None,
            account: "".into(),
            password: None,
            pass_salt: None,
            account_type: AccountType::Site,
            platform_info: None,
            up_time: None,
            created_at: DateTime::now(),
            updated_at: DateTime::now(),
        }
    }
}
